package rapier.sso.server.controller;

import java.util.Date;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;

import cn.hutool.core.util.RandomUtil;
import rapier.sso.common.utils.ThreadLocalUtil;
import rapier.sso.core.config.redis.RedisClient;
import rapier.sso.core.entity.TDbSsoAppEntity;
import rapier.sso.core.entity.TDbSysUserEntity;
import rapier.sso.core.model.AppTokenReqModel;
import rapier.sso.core.model.AppTokenRespModel;
import rapier.sso.core.model.ReturnModel;
import rapier.sso.core.model.UserLoginReqModel;
import rapier.sso.core.model.UserLoginRespModel;
import rapier.sso.core.model.UserModel;
import rapier.sso.core.service.ITDbSysUserLogService;
import rapier.sso.core.service.ITDbSysUserService;
import rapier.sso.server.constant.RapierComConstant;
import rapier.sso.server.constant.RapierServerConstant;
import rapier.sso.server.constant.RapierSsoBaseAppPath;
import rapier.sso.server.constant.RapierSsoBaseNoticePath;
import rapier.sso.server.env.RapierSsoServerEnv;
import rapier.sso.server.handler.IAppAuthenticationHandler;
import rapier.sso.server.model.AppCredential;
import rapier.sso.server.service.RapierAppsService;
import rapier.sso.server.utils.PasswordUtil;
import rapier.sso.server.utils.TokenUtils;
import rapier.sso.server.utils.UserModelUtlis;

/**
 * 面向应用
 * 
 * sso server
 *
 */
@Controller
public class SsoServerAppController {
	@Autowired
	private ITDbSysUserLogService userLogService;
	@Autowired
	private ITDbSysUserService userService;
	@Autowired
	private RedisClient redisClient;
	@Autowired
	private RapierAppsService appsService;
	@Autowired
	private RapierSsoServerEnv rapierSsoServerEnv;
	@Autowired
	private IAppAuthenticationHandler authAppHandler;

	@RequestMapping(value = RapierSsoBaseAppPath.PATH_APP_LOGIN, method = RequestMethod.POST)
	@ResponseBody
	public ReturnModel<AppTokenRespModel> appLogin(@RequestBody AppTokenReqModel reqModel) {
		TDbSsoAppEntity appEntity = appsService.getAppAuthInfo(reqModel.getId());
		if (!appEntity.getSecretKey().equals(reqModel.getSecretKey())) {
			return new ReturnModel<AppTokenRespModel>(500, "客户端秘钥错误!");
		}
		// 删除缓存
		redisClient.remove(RapierServerConstant.REDIS_RAPIER_APP_TICKET + appEntity.getId());
		redisClient.remove(RapierServerConstant.REDIS_RAPIER_APP_TICKET_GRAND + appEntity.getId());
		// 创建新的Token
		String appTicket = null;
		String appTicketGrand = null;
		if (reqModel.isRefeshToken()) {
			appTicketGrand = TokenUtils.makeAppTicketgrant(appEntity.getId());// 即刷新Token
			redisClient.set(RapierServerConstant.REDIS_RAPIER_APP_TICKET_GRAND + appEntity.getId(), appTicketGrand,
					(long) rapierSsoServerEnv.getClientRefreshTokenExpireHours(), TimeUnit.HOURS);
		}
		appTicket = TokenUtils.makeAppTicket(appEntity.getId(), appTicketGrand);// 即Token
		redisClient.set(RapierServerConstant.REDIS_RAPIER_APP_TICKET + appEntity.getId(), appTicketGrand,
				(long) rapierSsoServerEnv.getClientTokenExpireSeconds(), TimeUnit.SECONDS);
		// 构建返回信息
		AppTokenRespModel appRespModel = new AppTokenRespModel();
		if (reqModel.isRefeshToken()) {
			appRespModel.setRefeshToken(appTicketGrand);
		}
		appRespModel.setToken(appTicket);
		appRespModel.setExpires(rapierSsoServerEnv.getClientTokenExpireSeconds());
		appRespModel.setState(reqModel.getState());
		return new ReturnModel<AppTokenRespModel>(appRespModel);
	}

	@RequestMapping(value = RapierSsoBaseAppPath.PATH_APP_LOGOUT, method = RequestMethod.POST)
	@ResponseBody
	public ReturnModel<Void> appLogout(HttpServletRequest request) {
		String appTicket = request.getHeader(RapierComConstant.RAPIER_COOKIE_APP_TOKEN);
		if (StringUtils.isBlank(appTicket)) {
			new ReturnModel<Void>(500, "缺少Token信息!");
		}
		Long appId = TokenUtils.getAppId(appTicket);
		redisClient.remove(RapierServerConstant.REDIS_RAPIER_APP_TICKET_GRAND + appId);
		redisClient.remove(RapierServerConstant.REDIS_RAPIER_APP_TICKET + appId);
		return new ReturnModel<Void>(200, "客户端已经退出登录");
	}

	@RequestMapping(value = RapierSsoBaseAppPath.PATH_APP_REFRESH, method = RequestMethod.POST)
	@ResponseBody
	public ReturnModel<AppTokenRespModel> appRefresh(HttpServletRequest request) {
		String ticketgrand = request.getHeader(RapierComConstant.RAPIER_COOKIE_APP_REFRESH_TOKEN);
		if (StringUtils.isBlank(ticketgrand)) {
			new ReturnModel<Void>(500, "缺少Refresh_Token信息!");
		}
		long appId = TokenUtils.getAppId(ticketgrand);
		Object refresh = redisClient.get(RapierServerConstant.REDIS_RAPIER_APP_TICKET_GRAND + appId);
		if (refresh == null || !refresh.toString().equals(ticketgrand)) {
			return new ReturnModel<AppTokenRespModel>(402, "刷新Token已失效!");
		}
		// 创建新的Token
		String appTicketGrand = TokenUtils.makeAppTicketgrant(appId);// 即刷新Token
		redisClient.set(RapierServerConstant.REDIS_RAPIER_APP_TICKET_GRAND + appId, appTicketGrand,
				(long) rapierSsoServerEnv.getClientRefreshTokenExpireHours(), TimeUnit.HOURS);
		String appTicket = TokenUtils.makeAppTicket(appId, appTicketGrand);// 即Token
		redisClient.set(RapierServerConstant.REDIS_RAPIER_APP_TICKET + appId, appTicketGrand,
				(long) rapierSsoServerEnv.getClientTokenExpireSeconds(), TimeUnit.SECONDS);

		AppTokenRespModel appRespModel = new AppTokenRespModel();
		appRespModel.setRefeshToken(appTicketGrand);
		appRespModel.setToken(appTicket);
		appRespModel.setExpires(rapierSsoServerEnv.getClientTokenExpireSeconds());
		return new ReturnModel<AppTokenRespModel>(appRespModel);
	}

	/**
	 * userAdd
	 *
	 * @param serviceticket
	 * @param clientId
	 * @return
	 */
	@RequestMapping(value = RapierSsoBaseAppPath.PATH_USERADD, method = RequestMethod.POST)
	@ResponseBody
	public ReturnModel<Void> userAdd(@RequestBody UserModel userInfo) {
		QueryWrapper<TDbSysUserEntity> query = new QueryWrapper<TDbSysUserEntity>();
		query.eq("account", userInfo.getAccount());
		query.last("limit 1");
		int exist = userService.count(query);
		if (exist > 0) {
			return new ReturnModel<Void>(ReturnModel.FAIL_CODE, "账号已经存在!");
		}
		TDbSysUserEntity entity = new TDbSysUserEntity();
		BeanUtils.copyProperties(userInfo, entity);
		entity.setId(null);
		String salt = RandomUtil.randomString(8);
		String userpassword = PasswordUtil.encrypt(userInfo.getAccount(), userInfo.getPasswd(), salt);
		entity.setSalt(salt);
		entity.setPasswd(userpassword);
		userService.save(entity);
		sendAllClient(entity, RapierSsoBaseNoticePath.NOTICE_USECHANGE_ADD);// 通知新增
		return new ReturnModel<Void>(200, "操作成功");
	}

	/**
	 * userLogin
	 *
	 * @param serviceticket
	 * @param clientId
	 * @return
	 */
	@RequestMapping(value = RapierSsoBaseAppPath.PATH_USEREDIT, method = RequestMethod.POST)
	@ResponseBody
	public ReturnModel<Void> userEdit(HttpServletRequest request, @RequestBody UserModel userInfo) {
		TDbSsoAppEntity appEntity = ThreadLocalUtil.get(RapierComConstant.RAPIER_APP);
		UserModel model = validateTicket(request.getHeader(RapierComConstant.RAPIER_COOKIE_USER_TOKEN),
				appEntity.getId());
		if (model == null) {
			return new ReturnModel<Void>(1402, "用户Token失效!");
		}
		TDbSysUserEntity entity = userService.getById(userInfo.getId());
		if (entity == null) {
			return new ReturnModel<Void>(ReturnModel.FAIL_CODE, "账号不存在!");
		}
		if (!entity.getAccount().equals(userInfo.getAccount())) {
			return new ReturnModel<Void>(ReturnModel.FAIL_CODE, "账号不允许修改!");
		}
		// 更新
		TDbSysUserEntity update = new TDbSysUserEntity();
		BeanUtils.copyProperties(userInfo, update);
		QueryWrapper<TDbSysUserEntity> query = new QueryWrapper<TDbSysUserEntity>();
		query.eq("id", userInfo.getId());
		userService.update(update, query);
		BeanUtils.copyProperties(userInfo, model);
		redisClient.set(RapierServerConstant.REDIS_RAPIER_APP_TICKET + appEntity.getId() + ":" + entity.getId(), model);
		sendAllClient(update, RapierSsoBaseNoticePath.NOTICE_USECHANGE_EDIT);// 通知更新
		return new ReturnModel<Void>(200, "操作成功!");
	}

	/**
	 * userLogin
	 *
	 * @param serviceticket
	 * @param clientId
	 * @return
	 */
	@RequestMapping(value = RapierSsoBaseAppPath.PATH_USERDEL, method = RequestMethod.POST)
	@ResponseBody
	public ReturnModel<Void> userDel(HttpServletRequest request) {
		TDbSsoAppEntity appEntity = ThreadLocalUtil.get(RapierComConstant.RAPIER_APP);
		UserModel model = validateTicket(request.getHeader(RapierComConstant.RAPIER_COOKIE_USER_TOKEN),
				appEntity.getId());
		if (model == null) {
			return new ReturnModel<Void>(1402, "用户Token失效!");
		}
		TDbSysUserEntity entity = userService.getById(model.getId());
		if (entity == null) {
			return new ReturnModel<Void>(ReturnModel.SUCCESS_CODE, "删除成功");
		}
		redisClient.remove(RapierServerConstant.REDIS_RAPIER_APP_USER_TOKEN + appEntity.getId() + ":" + entity.getId());
		sendAllClient(entity, RapierSsoBaseNoticePath.NOTICE_USECHANGE_DEL);// 通知新增
		return new ReturnModel<Void>(ReturnModel.SUCCESS_CODE, "删除成功");
	}

	/**
	 * userLogin
	 *
	 * @param serviceticket
	 * @param clientId
	 * @return
	 * @throws Exception
	 */
	@RequestMapping(value = RapierSsoBaseAppPath.PATH_USERLOGIN, method = RequestMethod.POST)
	@ResponseBody
	public ReturnModel<UserLoginRespModel> userLogin(@RequestBody UserLoginReqModel userInfo,
			HttpServletRequest request) {
		AppCredential credential = new AppCredential(userInfo);
		UserModel loginUser = authAppHandler.authenticate(credential);
		if (loginUser == null) {
			return new ReturnModel<UserLoginRespModel>(ReturnModel.FAIL_CODE, credential.getError());
		} else {
			TDbSsoAppEntity appEntity = ThreadLocalUtil.get(RapierComConstant.RAPIER_APP);
			loginUser = UserModelUtlis.getClientModel(loginUser);
			String userToken = TokenUtils.makeToken(appEntity.getId(), loginUser.getId());
			redisClient.set(
					RapierServerConstant.REDIS_RAPIER_APP_USER_TOKEN + appEntity.getId() + ":" + loginUser.getId(),
					loginUser, (long) rapierSsoServerEnv.getClientTokenExpireSeconds(), TimeUnit.SECONDS);
			UserLoginRespModel model = new UserLoginRespModel();
			model.setToken(userToken);
			return new ReturnModel<UserLoginRespModel>(model);
		}
	}

	/**
	 * userLogin
	 *
	 * @param serviceticket
	 * @param clientId
	 * @return
	 * @throws Exception
	 */
	@RequestMapping(value = RapierSsoBaseAppPath.PATH_APP_LOGOUT, method = RequestMethod.POST)
	@ResponseBody
	public ReturnModel<Void> userLogout(@RequestBody UserLoginReqModel userInfo, HttpServletRequest request) {
		TDbSsoAppEntity appEntity = ThreadLocalUtil.get(RapierComConstant.RAPIER_APP);
		UserModel model = validateTicket(request.getHeader(RapierComConstant.RAPIER_COOKIE_USER_TOKEN),
				appEntity.getId());
		if (model == null) {
			return new ReturnModel<Void>(1402, "用户Token失效!");
		}
		redisClient.remove(RapierServerConstant.REDIS_RAPIER_APP_USER_TOKEN + appEntity.getId() + ":" + model.getId());
		return new ReturnModel<Void>(ReturnModel.SUCCESS_CODE, "成功登出!");
	}

	/**
	 * userLogin
	 *
	 * @param serviceticket
	 * @param clientId
	 * @return
	 * @throws Exception
	 */
	@RequestMapping(value = RapierSsoBaseAppPath.PATH_USERINFO, method = RequestMethod.POST)
	@ResponseBody
	public ReturnModel<UserModel> userInfo(@RequestBody UserLoginReqModel userInfo, HttpServletRequest request) {
		TDbSsoAppEntity appEntity = ThreadLocalUtil.get(RapierComConstant.RAPIER_APP);
		UserModel model = validateTicket(request.getHeader(RapierComConstant.RAPIER_COOKIE_USER_TOKEN),
				appEntity.getId());
		if (model == null) {
			return new ReturnModel<UserModel>(1402, "用户Token失效!");
		}
		return new ReturnModel<UserModel>(model);
	}

	private void sendAllClient(TDbSysUserEntity entity, String state) {
		// 保存变更日志
		TDbSysUserEntity userlogEntity = new TDbSysUserEntity();
		userlogEntity.setJson(JSONObject.toJSONString(entity));
		userlogEntity.setOptionTime(new Date());
		userlogEntity.setState(state);
		userlogEntity.setUserId(entity.getId());
		userLogService.save(userlogEntity);
		// 发送异步消息
		ClientSystem clientSystem = ThreadLocalUtil.get(RapierComConstant.RAPIER_CLIENT);
		for (ClientSystem index : clientsPool.getClients()) {
			if (clientSystem != null && clientSystem.getClient().getId().equals(index.getClient().getId())) {
				continue;
			}
			clientSystemService.noticeUserChange(clientSystem, entity.getId(), state);
		}
	}

	/**
	 * 验证ServiceTicket
	 * 
	 * @param stCookie
	 * @return
	 */
	private UserModel validateTicket(String ticket, Long appId) {
		if (StringUtils.isBlank(ticket)) {
			return null;
		}
		Long userId = TokenUtils.getUserId(ticket);
		if (appId == null) {
			Object stObj = redisClient.get(RapierServerConstant.REDIS_RAPIER_APP_USER_TOKEN + userId);
			if (stObj != null && UserModel.class.isInstance(stObj)) {
				return (UserModel) stObj;
			}
		} else {
			Object stObj = redisClient.get(RapierServerConstant.REDIS_RAPIER_APP_USER_TOKEN + appId + ":" + userId);
			if (stObj != null && UserModel.class.isInstance(stObj)) {
				return (UserModel) stObj;
			}
		}
		return null;
	}
}